﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;
using QUT.Bio.Web.SemanticCommon;
using SemWeb;
using SemWeb.Query;
using SemWeb.Remote;
using System.Text.RegularExpressions;

namespace QUT.Bio.Web {
	/// <summary>
	/// Summary description for RdfTagService
	/// </summary>
	[WebService( Namespace = "http://www.mquter.qut.edu.au/rdftag/" )]
	[WebServiceBinding( ConformsTo = WsiProfiles.BasicProfile1_1 )]
	// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
	// [System.Web.Script.Services.ScriptService]
	public class RdfTagService : System.Web.Services.WebService {
		private static Dictionary<string, List<string>> usefulGraphsForUriCache = new Dictionary<string, List<string>>();

		#region Constructor
		public RdfTagService () {
			//Uncomment the following line if using designed components 
			//InitializeComponent(); 
		}
		#endregion

		#region GetDummyCompletionList
		[WebMethod( EnableSession = true )]
		public string[] GetDummyCompletionList ( string prefixText, int count ) {
			if ( count == 0 ) {
				count = 10;
			}

			if ( prefixText.Equals( "xyz" ) ) {
				return new string[0];
			}

			Random random = new Random();
			List<string> items = new List<string>();
			for ( int i = 0; i < count; i++ ) {
				char c1 = (char) random.Next( 65, 90 );
				char c2 = (char) random.Next( 97, 122 );
				char c3 = (char) random.Next( 97, 122 );

				items.Add( prefixText + c1 + c2 + c3 );
			}

			//items.Add(LoginState.FriendlyLoginName);
			return items.ToArray();
		}
		#endregion

		#region GetLoggedInUserUri
		//http://msdn.microsoft.com/en-us/library/aa480509.aspx
		[WebMethod( EnableSession = true )]
		public string GetLoggedInUserUri () {
			if ( LoginState.AuthenticationResponse == null )
				return null;

			return LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
		}
		#endregion

		#region GetTagLabelAutoCompleteList
		[WebMethod( EnableSession = true )]
		public UriAndLabel[] GetTagLabelAutoCompleteList ( string prefixText, int count ) {
			SemanticResultsSink results;
			List<UriAndLabel> items = new List<UriAndLabel>();

			if ( !( prefixText.Length >= 3 ) ) {
				// return empty array
				return items.ToArray();
				// can't do fulltext searching with less than three characters per Virtuoso limitation
			}

			// don't do queries against URI's
			if ( Uri.IsWellFormedUriString( prefixText, UriKind.Absolute ) ) {
				return items.ToArray();
			}

			string userIdentifier = "<http://purl.org/net/terms#AnonymousUser>";
			string filterString = " FILTER(sameTerm(?foafUser, <http://purl.org/net/terms#AnonymousUser>)) ";


			if ( LoginState.AuthenticationResponse != null ) {
				// Peter_to_do: Emit some error message if they authenticated but the claimed identifier is not a URI
				if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
					userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
					filterString = " FILTER(sameTerm(?foafUser, <http://purl.org/net/terms#AnonymousUser>) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) \n";
				}
			}


			filterString += " FILTER(bif:contains(?tagLabelName, '\"" + prefixText + "*\"' ) ) ";

			string query = "    PREFIX tags: <http://www.holygoat.co.uk/owl/redwood/0.1/tags/> "
							+ " PREFIX terms: <http://purl.org/net/terms#> "
							+ " SELECT DISTINCT ?tagLabelUri ?tagLabelName "
							+ " WHERE "
							+ " { "
							+ "     GRAPH <http://purl.org/net/terms#TermInstances> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermUse . "
							+ "         ?taggingInstance terms:user ?foafUser . "
							+ "         ?taggingInstance terms:time ?taggedOn . "
							+ "         ?taggingInstance terms:subject ?taggedResource . "
							+ "         ?taggingInstance terms:term ?tagLabelUri . "
							+ "         ?tagLabelUri tags:name ?tagLabelName . "
							+ "         " + filterString
							+ "     } "
							+ " } "
							+ " LIMIT " + count + " ";
			//+ " ORDER BY ?tagLabelName ";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch {
				return new UriAndLabel[] { new UriAndLabel { ItemLabel = query } };
			}

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				//ListItem nextItem = new ListItem();
				UriAndLabel nextItem = new UriAndLabel();
				foreach ( Variable variable in resultRowBinding.Variables ) {

					if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.ItemLabel = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.ItemUri = resultRowBinding[variable].Uri;
					}
				}
				items.Add( nextItem );
			}

			return items.ToArray();
		}
		#endregion

		#region GetTagPredicateAutoCompleteList
		[WebMethod( EnableSession = true )]
		public UriAndLabel[] GetTagPredicateAutoCompleteList ( string prefixText, int count ) {
			SemanticResultsSink results;
			List<UriAndLabel> items = new List<UriAndLabel>();

			if ( !( prefixText.Length >= 3 ) ) {
				// return empty array
				return items.ToArray();
				// can't do fulltext searching with less than three characters per Virtuoso limitation
			}

			// don't do queries against URI's
			if ( Uri.IsWellFormedUriString( prefixText, UriKind.Absolute ) ) {
				return items.ToArray();
			}

			//string userIdentifier = "<http://purl.org/net/terms#AnonymousUser>";
			//string filterString = "";
			// FIXME_Peter: See bug below before enabling this level of restriction
			//string filterString = " FILTER(sameTerm(?foafUser, <http://purl.org/net/terms#AnonymousUser>)) ";

			/*
			// FIXME_Peter Peter_to_do: Can't check if they are logged in when they use the webservice for some reason
			if (SemanticUtility.loggedIn())
			{
				// Peter_to_do: Guarantee that claimed identifier is a valid URI, not sure if the XRI people guarantee this with their alternative forked syntax
				userIdentifier = "<" + State.AuthenticationResponse.ClaimedIdentifier.ToString() + ">";
				filterString = " FILTER(sameTerm(?foafUser, <http://purl.org/net/terms#AnonymousUser>) || sameTerm(?foafUser, " + userIdentifier + ") ) ";
			}
			*/

			// Peter_to_do: filter the users previously entered predicates using a full text search
			// Peter_to_do: get a version of the bio2rdf specification which includes full text comments and labels so you can full text search them
			string query = "    PREFIX tags: <http://www.holygoat.co.uk/owl/redwood/0.1/tags/> "
							+ " PREFIX terms: <http://purl.org/net/terms#> "
							+ " PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> "
							+ " PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>  "
							+ " PREFIX owl: <http://www.w3.org/2002/07/owl#> "
							+ " SELECT DISTINCT ?tagPredicate ?tagPredicateLabel "
							+ " WHERE "
							+ " { "
							+ "     { "
							+ "         GRAPH <http://purl.org/net/terms#TermInstances> "
							+ "         { "
							+ "             ?taggingInstance rdf:type tags:Tagging . "
							+ "             ?taggingInstance terms:user ?foafUser . "
							+ "             ?taggingInstance terms:predicate ?tagPredicate . "
							+ "         } "
							+ "         GRAPH ?anyGraph " // very slow
							+ "         { "
							+ "             ?tagPredicate rdfs:label ?tagPredicateLabel . "
							+ "             FILTER(bif:contains(?tagPredicateLabel, '\"" + prefixText + "*\"')) "
				//+ "             OPTIONAL{?tagPredicate rdfs:label ?tagPredicateLabel . FILTER(bif:contains(?tagPredicateLabel, '\"" + prefixText + "*\"')) } "
				//+ "             OPTIONAL{?tagPredicate tags:name ?tagPredicateName . FILTER(bif:contains(?tagPredicateName, '\"" + prefixText + "*\"')) } "
							+ "         } "
							+ "     } "
							+ "     UNION "
							+ "     { "
							+ "         GRAPH <http://dbpedia.org/property> "
							+ "         { "
							+ "             ?tagPredicate rdf:type rdf:Property . "
							+ "             ?tagPredicate rdfs:label ?tagPredicateLabel . "
							+ "             FILTER(bif:contains(?tagPredicateLabel, '\"" + prefixText + "*\"')) "
							+ "         } "
							+ "     } "
				/*+ "     UNION "
				+ "     { "
				+ "         GRAPH <http://bio2rdf.org/ns/bio2rdf#> "
				+ "         { "
				+ "             ?tagPredicate rdf:type owl:ObjectProperty . "
				+ "         } "
				+ "     } "
				+ "     UNION "
				+ "     { "
				+ "         GRAPH <http://bio2rdf.org/ns/bio2rdf#> "
				+ "         { "
				+ "             ?tagPredicate rdf:type owl:DatatypeProperty . "
				+ "         } "
				+ "     } " 
				*/
							+ " } "
							+ " LIMIT " + count + " ";
			//+ " ORDER BY ?tagLabelName \n";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch {
				UriAndLabel result = new UriAndLabel();
				result.ItemLabel = query;

				items.Add( result );
				return items.ToArray();
			}

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				//ListItem nextItem = new ListItem();
				UriAndLabel nextItem = new UriAndLabel();

				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "tagPredicate" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						//items.Add(resultRowBinding[variable].Uri);
						nextItem.ItemUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "tagPredicateLabel" ) ) {
						nextItem.ItemLabel = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
				}

				items.Add( nextItem );
			}



			return items.ToArray();
		}
		#endregion

		#region GetTagObjectAutoCompleteList
		[WebMethod( EnableSession = true )]
		public UriAndLabel[] GetTagObjectAutoCompleteList ( string prefixText, int count ) {
			SemanticResultsSink results;
			List<UriAndLabel> items = new List<UriAndLabel>();

			if ( !( prefixText.Length >= 3 ) ) {
				// return empty array
				return items.ToArray();
				// can't do fulltext searching with less than three characters per Virtuoso limitation
			}

			// don't do queries against URI's
			if ( Uri.IsWellFormedUriString( prefixText, UriKind.Absolute ) ) {
				return items.ToArray();
			}

			//string userIdentifier = "<http://purl.org/net/terms#AnonymousUser>";
			//string filterString = "";
			// FIXME_Peter: See bug below before enabling this level of restriction
			//string filterString = " FILTER(sameTerm(?foafUser, <http://purl.org/net/terms#AnonymousUser>)) \n";

			/*
			// FIXME_Peter Peter_to_do: Can't check if they are logged in when they use the webservice for some reason
			if (SemanticUtility.loggedIn())
			{
				// Peter_to_do: Guarantee that claimed identifier is a valid URI, not sure if the XRI people guarantee this with their alternative forked syntax
				userIdentifier = "<" + State.AuthenticationResponse.ClaimedIdentifier.ToString() + ">";
				filterString = " FILTER(sameTerm(?foafUser, <http://purl.org/net/terms#AnonymousUser>) || sameTerm(?foafUser, " + userIdentifier + ") ) \n";
			}
			*/

			// Peter_to_do: filter the users previously entered predicated using a full text search
			string query = "    PREFIX tags: <http://www.holygoat.co.uk/owl/redwood/0.1/tags/> "
							+ " PREFIX terms: <http://purl.org/net/terms#> "
							+ " PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> "
							+ " SELECT DISTINCT ?tagObject ?tagObjectLabel ?tagObjectDCTitle ?tagObjectComment ?tagObjectDCIdentifier "
							+ " WHERE "
							+ " { "
							+ "     { "
							+ "         GRAPH <http://purl.org/net/terms#TermInstances> "
							+ "         { \n"
							+ "             ?taggingInstance rdf:type tags:Tagging . "
							+ "             ?taggingInstance terms:user ?foafUser . "
							+ "             ?taggingInstance terms:object ?tagObject . "
							+ "         } "
							+ "     } "
							+ "     UNION "
							+ "     { "
							+ "         GRAPH ?anyGraph "
							+ "         { "
							+ "             OPTIONAL{?tagObject <http://www.w3.org/2000/01/rdf-schema#label> ?tagObjectLabel . FILTER(bif:contains(?tagObjectLabel, '\"" + prefixText + "*\"'))} "
							+ "             OPTIONAL{?tagObject <http://purl.org/dc/elements/1.1/title> ?tagObjectDCTitle . FILTER(bif:contains(?tagObjectDCTitle, '\"" + prefixText + "*\"'))} "
							+ "             OPTIONAL{?tagObject <http://www.w3.org/2000/01/rdf-schema#comment> ?tagObjectComment . FILTER(bif:contains(?tagObjectComment, '\"" + prefixText + "*\"'))} "
							+ "             OPTIONAL{?tagObject <http://purl.org/dc/elements/1.1/identifier> ?tagObjectDCIdentifier . FILTER(bif:contains(?tagObjectDCIdentifier, '\"" + prefixText + "*\"'))} "
							+ "         } "
							+ "     } "
							+ " } "
							+ " LIMIT " + count + " ";
			//+ " ORDER BY ?tagLabelName \n";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch {
				UriAndLabel result = new UriAndLabel();
				result.ItemLabel = query;

				items.Add( result );
				return items.ToArray();
			}

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				//ListItem nextItem = new ListItem();
				UriAndLabel nextItem = new UriAndLabel();

				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "tagObject" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						//items.Add(resultRowBinding[variable].Uri);
						nextItem.ItemUri = resultRowBinding[variable].Uri;
					}
					// Peter_to_do: figure out a resolution strategy for multiple possible labels 
					else if ( variable.LocalName.Equals( "tagObjectLabel" ) )// || variable.LocalName.Equals("tagObjectDCTitle") || variable.LocalName.Equals("tagObjectComment") || variable.LocalName.Equals("tagObjectDCIdentifier"))
                    {
						nextItem.ItemLabel = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}

				}
				items.Add( nextItem );
			}



			return items.ToArray();
		}
		#endregion

		#region GetGroupsForUser
		[WebMethod( EnableSession = true )]
		public GroupInformation[] GetGroupsForUser ( string userIdentifierUri ) {
			List<GroupInformation> result = new List<GroupInformation>();

			// for security we don't let them look at another users group list... although for testing this is not strictly enabled
			if ( LoginState.AuthenticationResponse != null ) {
				userIdentifierUri = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
			}
			// Peter_to_do: uncomment me when not testing, particularly after the tagging web service is integrated with the other applications to be able to share the session information
			//else
			//{
			//    return result.ToArray();
			//}

			string filterString = " FILTER(sameTerm(?foafUser, <" + userIdentifierUri + "> )) ";

			string groupsQuery = " PREFIX terms: <http://purl.org/net/terms#> "
						+ " PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> "
						+ " SELECT ?group ?groupName "
						+ " WHERE "
						+ " { "
						+ "     GRAPH <http://purl.org/net/terms#GroupList> "
						+ "     { "
						+ "         ?group rdf:type terms:Group . "
						+ "         ?group rdfs:label ?groupName . "
						+ "     } "
						+ "     GRAPH <http://purl.org/net/terms#GroupMemberList> "
						+ "     { "
						+ "         ?foafUser terms:memberOf ?group . "
						+ "         " + filterString
						+ "     } "
						+ " } "
						+ " LIMIT 200 ";

			SemanticResultsSink groupsForThisOpenID;

			try {

				groupsForThisOpenID = CommonSemanticOperations.doQuery( groupsQuery );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + groupsQuery, ex );
			}

			foreach ( VariableBindings resultRowBinding in groupsForThisOpenID.resultsList ) {
				GroupInformation nextItem = new GroupInformation();

				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "groupName" ) ) {
						nextItem.GroupName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "group" ) ) {
						nextItem.GroupUri = resultRowBinding[variable].Uri;
					}
				}

				result.Add( nextItem );
			}

			//if (result.Count > 0)
			//{
			//    GroupCheckListLabel.Text = "Choose groups to share the tag with: (Optional)";
			//}

			return result.ToArray();
		}
		#endregion

		#region SearchForTagsByLabel
		[WebMethod( EnableSession = true )]
		public RdfTag[] SearchForTagsByLabel ( string searchTerm, int maxNumberOfResults, int startOffset ) {
			List<RdfTag> results = new List<RdfTag>();

			String fetchUrl = "http://bio2rdf.org/searchns/term:" + System.Uri.EscapeDataString( searchTerm );

			Statement[] rdfFetchResults = CommonSemanticOperations.getRdfFromUrl( fetchUrl );

			foreach ( Statement nextFetchResult in rdfFetchResults ) {
				if ( nextFetchResult.Predicate.Equals( Entity_Constants.rdfsSeeAlso ) ) {
					Statement[] individualRdfFetchResults = CommonSemanticOperations.getRdfFromUrl( SemanticUtility.resolveResourceReferenceString( nextFetchResult.Object ) );

					RdfTag tagObject = new RdfTag( individualRdfFetchResults );

					results.Add( tagObject );
				}
			}

			return results.ToArray();
		}
		#endregion

		#region GetTagInstancesByLabelUri
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagInstancesByLabelUri ( string tagLabelUri, int maxNumberOfResults, int startOffset ) {
			String tagLabelUriInput = "";

			SemanticResultsSink results;
			List<RdfTag> items = new List<RdfTag>();

			string userIdentifier = Entity_Constants.termsAnonymousUser.Uri;
			string filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + ">)) ";

			if ( LoginState.AuthenticationResponse != null ) {
				// Peter_to_do: Emit some error message if they authenticated but the claimed identifier is not a URI
				if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
					userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
					filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) \n";
				}
			}


			// don't do queries against URI's
			if ( Uri.IsWellFormedUriString( tagLabelUri, UriKind.Absolute ) ) {
				tagLabelUriInput = tagLabelUri;
			}
			else {
				throw new Exception( "The tag label URI must be a valid URI" );
			}


			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT ?tagLabelName ?taggingInstance ?foafUser ?taggedOn ?taggedResource ?termPredicate ?termObject"
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermInstance . "
							+ "         ?taggingInstance terms:taggedBy ?foafUser . "
							+ "         ?taggingInstance terms:taggedOn ?taggedOn . "
							+ "         ?taggingInstance terms:subject ?taggedResource . "
							+ "         OPTIONAL{?taggingInstance terms:predicate ?termPredicate .} "
							+ "         OPTIONAL{?taggingInstance terms:object ?termObject .} "
							+ "         ?taggingInstance terms:term <" + tagLabelUriInput + "> . "
							+ "         <" + tagLabelUriInput + "> tags:name ?tagLabelName . "
							+ "         " + filterString
							+ "     } "
							+ " } ";
			//+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			//try {
			results = CommonSemanticOperations.doQuery( query );
			//}
			//catch ( Exception ex ) {
			//    throw new Exception( "Query failed:" + query );
			//    //RdfTag resultTag = new RdfTag();
			//    //resultTag.TagLabelName = query;

			//    //return resultTag;

			//    //UriAndLabel result = new UriAndLabel();
			//    //result.ItemLabel = query;

			//    //items.Add(resultTag);
			//    //return items.ToArray();
			//}

			items.Capacity = results.resultsList.Count;

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {

				//VariableBindings resultRowBinding = results.resultsList[0];
				//ListItem nextItem = new ListItem();
				//UriAndLabel nextItem = new UriAndLabel();
				RdfTag nextItem = new RdfTag();
				foreach ( Variable variable in resultRowBinding.Variables ) {

					if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagLabelName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagLabelUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggingInstance" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						if ( resultRowBinding[variable] != null )
							nextItem.TagInstanceUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "foafUser" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.UserIdentifier = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggedOn" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagCreationTime = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "taggedResource" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagSubjectUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termPredicate" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagPredicateUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termObject" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagObjectUri = resultRowBinding[variable].Uri;
					}
				}

				nextItem.TagLabelUri = tagLabelUriInput;

				items.Add( nextItem );
			}

			//return nextItem;

			return items.ToArray();
		}
		#endregion

		#region GetTagInstancesByLabelUris
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagInstancesByLabelUris ( string[] tagLabelUris, int maxNumberOfResultsPerLabelUri, int startOffsetPerLabelUri ) {
			List<RdfTag> results = new List<RdfTag>();

			foreach ( string nextTagLabelUri in tagLabelUris ) {
				results.AddRange( GetTagInstancesByLabelUri( nextTagLabelUri, maxNumberOfResultsPerLabelUri, startOffsetPerLabelUri ) );
			}

			return results.ToArray();
		}

		#endregion

		#region GetTagsBySubjects
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagsBySubjects ( string[] subjectUris, int maxNumberOfResultsPerSubject, int startOffsetPerSubject ) {
			List<RdfTag> results = new List<RdfTag>();

			foreach ( string nextSubjectUri in subjectUris ) {
				results.AddRange( GetTagsBySubject( nextSubjectUri, maxNumberOfResultsPerSubject, startOffsetPerSubject ) );
			}

			return results.ToArray();
		}
		#endregion

		#region GetTagsBySubject
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagsBySubject ( string subjectUri, int maxNumberOfResults, int startOffset ) {

			String tagSubjectUriInput = "";

			SemanticResultsSink results;
			List<RdfTag> items = new List<RdfTag>();

			string userIdentifier = Entity_Constants.termsAnonymousUser.Uri;
			string filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + ">)) ";

			if ( LoginState.AuthenticationResponse != null ) {
				// Peter_to_do: Emit some error message if they authenticated but the claimed identifier is not a URI
				if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
					userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
					filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) \n";
				}
			}


			// don't do queries against URI's
			if ( Uri.IsWellFormedUriString( subjectUri, UriKind.Absolute ) ) {
				tagSubjectUriInput = subjectUri;
			}
			else {
				throw new Exception( "Subject must be a valid URI" );
			}


			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT ?tagLabelUri ?tagLabelName ?taggingInstance ?foafUser ?taggedOn ?termPredicate ?termObject"
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermInstance . "
							+ "         ?taggingInstance terms:taggedBy ?foafUser . "
							+ "         ?taggingInstance terms:taggedOn ?taggedOn . "
							+ "         ?taggingInstance terms:subject <" + tagSubjectUriInput + "> . "
							+ "         OPTIONAL{?taggingInstance terms:predicate ?termPredicate .} "
							+ "         OPTIONAL{?taggingInstance terms:object ?termObject .} "
							+ "         ?taggingInstance terms:term ?tagLabelUri . "
							+ "         ?tagLabelUri tags:name ?tagLabelName . "
							+ "         " + filterString
							+ "     } "
							+ " } "
							+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
				//RdfTag resultTag = new RdfTag();
				//resultTag.TagLabelName = query;
				//UriAndLabel result = new UriAndLabel();
				//result.ItemLabel = query;

				//items.Add(resultTag);
				//return items.ToArray();
			}

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				//ListItem nextItem = new ListItem();
				//UriAndLabel nextItem = new UriAndLabel();
				RdfTag nextItem = new RdfTag();
				foreach ( Variable variable in resultRowBinding.Variables ) {

					if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagLabelName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.TagLabelUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggingInstance" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagInstanceUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "foafUser" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.UserIdentifier = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggedOn" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagCreationTime = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "taggedResource" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagSubjectUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termPredicate" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagPredicateUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termObject" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagObjectUri = resultRowBinding[variable].Uri;
					}
				}

				nextItem.TagSubjectUri = tagSubjectUriInput;

				items.Add( nextItem );
			}



			return items.ToArray();
		}
		#endregion

		#region GetTagsBySubject
		[WebMethod( EnableSession = true )]
		public UriAndLabel[] GetTagLabelsBySubject ( string subjectUri ) {
			RdfTag[] allRelevantTags = GetTagsBySubject( subjectUri, Int16.MaxValue, 0 );

			List<string> usedLabelUris = new List<string>();

			List<UriAndLabel> results = new List<UriAndLabel>();

			foreach ( RdfTag nextRelevantTag in allRelevantTags ) {
				if ( usedLabelUris.Contains( nextRelevantTag.TagLabelUri ) )
					continue;
				UriAndLabel nextUriAndLabel = new UriAndLabel();
				nextUriAndLabel.ItemUri = nextRelevantTag.TagLabelUri;
				nextUriAndLabel.ItemLabel = nextRelevantTag.TagLabelName;

				results.Add( nextUriAndLabel );
			}

			return results.ToArray();
		}
		#endregion

		#region GetRelevantTagLabelsBySubjects
		/// <summary>
		/// Selects a set of relevant labels to the subject URI's as given. 
		/// This is used in a tag browser to given an indication of other tags 
		/// which are related to a given set of resources which have been 
		/// previously used as subjects on tags.
		/// </summary>
		/// <param name="subjectUris">A set of URI's to use for the comparison</param>
		/// <param name="maxNumberOfResults">The number of results to return for each subjectURI. Note, this is not implemented consistently and up to subjectUris.Length RdfTags could actually be returned.</param>
		/// <param name="startOffset">An offset, used for paging results. May not be implemented for all operations.</param>
		/// <param name="tagsAreRelevantToAllSubjects">A boolean variable which is true if the tags that returned are required to be relevant to all of the subjects. If it is false the set of tags which relate to any of the given subjects are returned.</param>
		/// <returns></returns>
		[WebMethod( EnableSession = true )]
		public UriAndLabel[] GetRelevantTagLabelsBySubjects ( string[] subjectUris, int maxNumberOfResults, int startOffset, bool tagsAreRelevantToAllSubjects ) {
			List<RdfTag> allTagsBySubjects = new List<RdfTag>();

			// start off with all of the tags on any of the subject URI's that were were given, limited by maxNumberOfResults on each one for efficiency
			// NOTE: if things appear to be playing up, it could be the use of maxNumberOfResults in this call
			// Research Question: what are the downsides to restricting this here given the existence requirement in the ALL_RELEVANT part of this function
			allTagsBySubjects.AddRange( GetTagsBySubjects( subjectUris, maxNumberOfResults, startOffset ) );

			Dictionary<string, UriAndLabel> resultDictionary = new Dictionary<string, UriAndLabel>();

			// if we are just returning all of the tags on all of the subject URI's then we just combine the results from each of the other 
			if ( !tagsAreRelevantToAllSubjects ) {
				foreach ( RdfTag nextRelevantTagInRange in allTagsBySubjects ) {
					// add one entry in the result dictionary for each tag label URI and tag label name that we found
					if ( !resultDictionary.ContainsKey( nextRelevantTagInRange.TagLabelUri ) ) {
						UriAndLabel nextKeyToAdd = new UriAndLabel();

						nextKeyToAdd.ItemUri = nextRelevantTagInRange.TagLabelUri;
						nextKeyToAdd.ItemLabel = nextRelevantTagInRange.TagLabelName;

						resultDictionary.Add( nextKeyToAdd.ItemUri, nextKeyToAdd );
					}
				}
			}
			else {
				Dictionary<string, List<RdfTag>> allLabelUrisWithTags = new Dictionary<string, List<RdfTag>>();

				// do an initial aggregation of all of the result tags to get a dictionary aggregated by labelUri
				foreach ( RdfTag nextTag in allTagsBySubjects ) {
					if ( !allLabelUrisWithTags.ContainsKey( nextTag.TagLabelUri ) ) {
						allLabelUrisWithTags[nextTag.TagLabelUri] = new List<RdfTag>();
					}

					allLabelUrisWithTags[nextTag.TagLabelUri].Add( nextTag );
				}


				// for each label that appeared on the tags that were relevant to any of the subjects
				foreach ( string labelUri in allLabelUrisWithTags.Keys ) {
					// assume this is true, until we find one of the subjects which didn't have any tags that contained this labelUri
					bool stillRelevantForLabel = true;

					UriAndLabel stillRelevantUriAndLabel = new UriAndLabel();

					// go back through the list of subject URI's
					foreach ( string nextSubject in subjectUris ) {
						bool stillRelevantForSubject = false;
						// and compare them against the list of tags that had this label used in them
						foreach ( RdfTag nextDictionaryTag in allLabelUrisWithTags[labelUri] ) {
							// and determine whether there was a place that the subject was used in that set of tags
							if ( nextDictionaryTag.TagSubjectUri.Equals( nextSubject ) ) {
								stillRelevantForSubject = true;
								break;
							}
						}

						// if we don't think the label is relevant for this subject we make sure that it is not thought to be relevant for this label either
						if ( !stillRelevantForSubject ) {
							stillRelevantForLabel = false;
							break;
						}
					}

					// if any of the tags which had this label, were tagged onto the subject in question then we include them in the results
					if ( stillRelevantForLabel ) {
						// it does not matter which of the tags, as they all contain by specification a one to one relationship between tagLabelName and tagLabelUri
						stillRelevantUriAndLabel.ItemLabel = allLabelUrisWithTags[labelUri][0].TagLabelName;
						stillRelevantUriAndLabel.ItemUri = allLabelUrisWithTags[labelUri][0].TagLabelUri;

						resultDictionary.Add( stillRelevantUriAndLabel.ItemUri, stillRelevantUriAndLabel );
					}
				}
			}

			return resultDictionary.Values.ToArray();
		}
		#endregion

		#region GetAllTagLabels
		[WebMethod( EnableSession = true )]
		public UriAndLabelAndCount[] GetCompleteTagCloud ( bool restrictToAccessibleTags ) {
			// Peter_to_do: implement the functionality for restrictToAccessibleTags

			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT ?tagLabelUri ?tagLabelName COUNT(?taggingInstance) AS ?tagFrequency "
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermInstance . "
							+ "         ?taggingInstance terms:term ?tagLabelUri . "
							+ "         ?tagLabelUri tags:name ?tagLabelName . "
				//+ "         " + filterString
							+ "     } "
							+ " } ";
			//                            + " LIMIT " + maxNumberOfResults + " ";

			SemanticResultsSink results;

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
			}

			List<UriAndLabelAndCount> resultsList = new List<UriAndLabelAndCount>( results.resultsList.Count );

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				UriAndLabelAndCount nextItem = new UriAndLabelAndCount();

				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.ItemUri = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.ItemLabel = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagFrequency" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.ItemCount = int.Parse( SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] ) );
					}
				}

				resultsList.Add( nextItem );
			}

			return resultsList.ToArray();
		}
		#endregion

		#region GetTagCloudForLabelUris
		[WebMethod( EnableSession = true )]
		public UriAndLabelAndCount[] GetTagCloudForLabelUris ( string[] tagLabelUris, bool restrictToAccessibleTags ) {
			List<UriAndLabelAndCount> resultList = new List<UriAndLabelAndCount>( tagLabelUris.Length );

			foreach ( string nextTagLabelUri in tagLabelUris ) {
				UriAndLabelAndCount nextResult = new UriAndLabelAndCount();

				nextResult.ItemUri = nextTagLabelUri;

				nextResult.ItemCount = GetTagLabelUriUseFrequency( nextTagLabelUri, restrictToAccessibleTags ).ItemCount;

				SubjectPredicateObject[] allLabels = GetLabelsForUri( nextTagLabelUri );

				if ( allLabels.Length > 0 ) {
					// by convention use the first one... if they want a complete list they will call GetLabelsForUri
					nextResult.ItemLabel = allLabels[0].ObjectResource;
				}

				resultList.Add( nextResult );
			}

			return resultList.ToArray();
		}
		#endregion

		#region GetTagLabelUriUseFrequencies
		[WebMethod( EnableSession = true )]
		public UriAndCount[] GetTagLabelUriUseFrequencies ( string[] tagLabelUris, bool restrictToAccessibleTags ) {
			List<UriAndCount> resultList = new List<UriAndCount>( tagLabelUris.Length );

			foreach ( string nextTagLabelUri in tagLabelUris ) {
				resultList.Add( GetTagLabelUriUseFrequency( nextTagLabelUri, restrictToAccessibleTags ) );
			}

			return resultList.ToArray();
		}
		#endregion

		#region GetTagLabelUriUseFrequency
		[WebMethod( EnableSession = true )]
		public UriAndCount GetTagLabelUriUseFrequency ( string tagLabelUri, bool restrictToAccessibleTags ) {
			SemanticResultsSink results;

			string userIdentifier = "<" + Entity_Constants.termsAnonymousUser.Uri + ">";
			// default to no restriction on ?foafUser because everything is anonymised in this method 
			// and it is not a hassle to get statistics for tagging instances we can't see with this user
			string filterString = "";
			//string filterString = " FILTER(sameTerm(?foafUser, <"+Entity_Constants.termsAnonymousUser.Uri+"> )) ";

			if ( restrictToAccessibleTags ) {
				if ( LoginState.AuthenticationResponse != null ) {
					if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
						userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
						filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) \n";
					}
					else {
						throw new UriFormatException( "OpenID was not a URI : " + LoginState.AuthenticationResponse.ClaimedIdentifier.ToString() );
					}
				}
				else {
					filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> )) ";
				}
				// fallback for testing purposes
				//else if (Uri.IsWellFormedUriString(userIdentifierUri, UriKind.Absolute))
				//{
				//    userIdentifier = userIdentifierUri;
				//    filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) ";
				//}
			}

			if ( !Uri.IsWellFormedUriString( tagLabelUri, UriKind.Absolute ) ) {
				throw new UriFormatException( "Tag Label URI was not a valid URI : " + tagLabelUri );
			}

			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT COUNT(?taggingInstance) as ?tagLabelUriCount"
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermInstance . "
							+ "         ?taggingInstance terms:taggedBy ?foafUser . "
							+ "         ?taggingInstance terms:term <" + tagLabelUri + "> . "
							+ "         " + filterString
							+ "     } "
							+ " } ";
			//+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
				//RdfTag resultTag = new RdfTag();
				//resultTag.TagLabelName = query;
				//UriAndLabel result = new UriAndLabel();
				//result.ItemLabel = query;

				//items.Add(resultTag);
				//return items.ToArray();
			}

			UriAndCount nextItem = new UriAndCount();

			if ( results.resultsList.Count > 1 ) {
				throw new Exception( "Invalid number of results returned from query: " + results.resultsList.Count );
			}
			else if ( results.resultsList.Count == 0 ) {
				// if no results came back we signal this with 0, which is a valid case
				nextItem.ItemUri = tagLabelUri;
				nextItem.ItemCount = 0;
			}
			else {
				foreach ( VariableBindings resultRowBinding in results.resultsList ) {
					//ListItem nextItem = new ListItem();
					//UriAndLabel nextItem = new UriAndLabel();
					//RdfTag nextItem = new RdfTag();

					foreach ( Variable variable in resultRowBinding.Variables ) {
						if ( variable.LocalName.Equals( "tagLabelUriCount" ) ) {
							nextItem.ItemCount = int.Parse( SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] ) );
						}
					}

					nextItem.ItemUri = tagLabelUri;
				}
			}

			return nextItem;
		}
		#endregion

		#region GetTagsCreatedByUser
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagsCreatedByUser ( string userIdentifierUri, int maxNumberOfResults, int startOffset ) {
			throw new NotImplementedException( "Peter_to_do: Implement me!!" );
		}
		#endregion

		#region GetTagsAccessibleToUser
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagsAccessibleToUser ( string userIdentifierUri, int maxNumberOfResults, int startOffset ) {
			SemanticResultsSink results;
			List<RdfTag> items = new List<RdfTag>();

			string userIdentifier = "<" + Entity_Constants.termsAnonymousUser.Uri + ">";
			string filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> )) ";

			if ( LoginState.AuthenticationResponse != null ) {
				if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
					userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
					filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) \n";
				}
				else {
					throw new UriFormatException( "OpenID was not a URI : " + LoginState.AuthenticationResponse.ClaimedIdentifier.ToString() );
				}
			}
			// fallback for testing purposes
			else if ( Uri.IsWellFormedUriString( userIdentifierUri, UriKind.Absolute ) ) {
				userIdentifier = userIdentifierUri;
				filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) ";
			}

			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT ?tagLabelUri ?tagLabelName ?taggingInstance ?foafUser ?taggedOn ?taggedResource ?termPredicate ?termObject"
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermInstance . "
							+ "         ?taggingInstance terms:taggedBy ?foafUser . "
							+ "         ?taggingInstance terms:taggedOn ?taggedOn . "
							+ "         ?taggingInstance terms:subject ?taggedResource . "
							+ "         OPTIONAL{?taggingInstance terms:predicate ?termPredicate .} "
							+ "         OPTIONAL{?taggingInstance terms:object ?termObject .} "
							+ "         ?taggingInstance terms:term ?tagLabelUri . "
							+ "         ?tagLabelUri tags:name ?tagLabelName . "
							+ "         " + filterString
							+ "     } "
							+ " } "
							+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
				//RdfTag resultTag = new RdfTag();
				//resultTag.TagLabelName = query;
				//UriAndLabel result = new UriAndLabel();
				//result.ItemLabel = query;

				//items.Add(resultTag);
				//return items.ToArray();
			}

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				//ListItem nextItem = new ListItem();
				//UriAndLabel nextItem = new UriAndLabel();
				RdfTag nextItem = new RdfTag();
				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagLabelName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.TagLabelUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggingInstance" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagInstanceUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "foafUser" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.UserIdentifier = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggedOn" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagCreationTime = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "taggedResource" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.TagSubjectUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termPredicate" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;

						if ( resultRowBinding[variable] != null )
							nextItem.TagPredicateUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termObject" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagObjectUri = resultRowBinding[variable].Uri;
					}
				}
				items.Add( nextItem );
			}

			return items.ToArray();
		}
		#endregion

		#region GetTagsAccessibleToLoggedInUser
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagsAccessibleToLoggedInUser ( int maxNumberOfResults, int startOffset, bool includeAnonymous ) {
			SemanticResultsSink results;
			List<RdfTag> items = new List<RdfTag>();

			string userIdentifier = String.Format( "<{0}>", Entity_Constants.termsAnonymousUser.Uri );
			string filterString = String.Format( " FILTER(sameTerm(?foafUser, <{0}> )) ", Entity_Constants.termsAnonymousUser.Uri );

			if ( LoginState.AuthenticationResponse != null ) {
				if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
					userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
					filterString = string.Format( " FILTER({0} {1})\n",
						includeAnonymous ? String.Format( "sameTerm(?foafUser, <{0}>) ||", Entity_Constants.termsAnonymousUser.Uri ) : "",
						string.Format( "sameTerm(?foafUser, <{0}>)", userIdentifier )
					);
				}
				else {
					throw new UriFormatException( "OpenID was not a URI : " + LoginState.AuthenticationResponse.ClaimedIdentifier.ToString() );
				}
			}

			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT ?tagLabelUri ?tagLabelName ?taggingInstance ?foafUser ?taggedOn ?taggedResource ?termPredicate ?termObject"
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         ?taggingInstance rdf:type terms:TermInstance . "
							+ "         ?taggingInstance terms:taggedBy ?foafUser . "
							+ "         ?taggingInstance terms:taggedOn ?taggedOn . "
							+ "         ?taggingInstance terms:subject ?taggedResource . "
							+ "         OPTIONAL{?taggingInstance terms:predicate ?termPredicate .} "
							+ "         OPTIONAL{?taggingInstance terms:object ?termObject .} "
							+ "         ?taggingInstance terms:term ?tagLabelUri . "
							+ "         ?tagLabelUri tags:name ?tagLabelName . "
							+ "         " + filterString
							+ "     } "
							+ " } "
							+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
				//RdfTag resultTag = new RdfTag();
				//resultTag.TagLabelName = query;
				//UriAndLabel result = new UriAndLabel();
				//result.ItemLabel = query;

				//items.Add(resultTag);
				//return items.ToArray();
			}

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				//ListItem nextItem = new ListItem();
				//UriAndLabel nextItem = new UriAndLabel();
				RdfTag nextItem = new RdfTag();
				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagLabelName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.TagLabelUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggingInstance" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagInstanceUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "foafUser" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.UserIdentifier = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggedOn" ) ) {
						// this isn't the part we actually want to send back, have to find out how to deal with this well
						//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
						nextItem.TagCreationTime = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "taggedResource" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						nextItem.TagSubjectUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termPredicate" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;

						if ( resultRowBinding[variable] != null )
							nextItem.TagPredicateUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termObject" ) ) {
						//nextItem.Value = resultRowBinding[variable].Uri;
						if ( resultRowBinding[variable] != null )
							nextItem.TagObjectUri = resultRowBinding[variable].Uri;
					}
				}
				items.Add( nextItem );
			}

			return items.ToArray();
		}
		#endregion

		#region GetTagInstanceByUri
		[WebMethod( EnableSession = true )]
		public RdfTag GetTagInstanceByUri ( string tagInstanceUri ) {

			String tagInstanceUriInput = "";

			SemanticResultsSink results;
			//List<RdfTag> items = new List<RdfTag>(1);

			string userIdentifier = Entity_Constants.termsAnonymousUser.Uri;
			string filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + ">)) ";

			if ( LoginState.AuthenticationResponse != null ) {
				// Peter_to_do: Emit some error message if they authenticated but the claimed identifier is not a URI
				if ( Uri.IsWellFormedUriString( LoginState.AuthenticationResponse.ClaimedIdentifier, UriKind.Absolute ) ) {
					userIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
					filterString = " FILTER(sameTerm(?foafUser, <" + Entity_Constants.termsAnonymousUser.Uri + "> ) || sameTerm(?foafUser, <" + userIdentifier + "> ) ) \n";
				}
			}


			// don't do queries against URI's
			if ( Uri.IsWellFormedUriString( tagInstanceUri, UriKind.Absolute ) ) {
				tagInstanceUriInput = tagInstanceUri;
			}
			else {
				throw new Exception( "The tag instance URI must be a valid URI" );
			}


			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT ?tagLabelUri ?tagLabelName ?foafUser ?taggedOn ?taggedResource ?termPredicate ?termObject"
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         <" + tagInstanceUriInput + "> rdf:type terms:TermInstance . "
							+ "         <" + tagInstanceUriInput + "> terms:taggedBy ?foafUser . "
							+ "         <" + tagInstanceUriInput + "> terms:taggedOn ?taggedOn . "
							+ "         <" + tagInstanceUriInput + "> terms:subject ?taggedResource . "
							+ "         OPTIONAL{<" + tagInstanceUriInput + "> terms:predicate ?termPredicate .} "
							+ "         OPTIONAL{<" + tagInstanceUriInput + "> terms:object ?termObject .} "
							+ "         <" + tagInstanceUriInput + "> terms:term ?tagLabelUri . "
							+ "         ?tagLabelUri tags:name ?tagLabelName . "
							+ "         " + filterString
							+ "     } "
							+ " } ";
			//+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
				//RdfTag resultTag = new RdfTag();
				//resultTag.TagLabelName = query;

				//return resultTag;

				//UriAndLabel result = new UriAndLabel();
				//result.ItemLabel = query;

				//items.Add(resultTag);
				//return items.ToArray();
			}

			//foreach (VariableBindings resultRowBinding in results.resultsList)
			//{
			if ( results.resultsList.Count == 0 ) {
				throw new Exception( "No results returned for this tag instance: " + tagInstanceUriInput + " Do you have access to it?" );
			}

			if ( results.resultsList.Count > 1 ) {
				throw new Exception( "More than one result returned for this tagging instance. Database needs examination. Tag Instance Uri was:" + tagInstanceUriInput );
			}

			VariableBindings resultRowBinding = results.resultsList[0];
			//ListItem nextItem = new ListItem();
			//UriAndLabel nextItem = new UriAndLabel();
			RdfTag nextItem = new RdfTag();
			foreach ( Variable variable in resultRowBinding.Variables ) {

				if ( variable.LocalName.Equals( "tagLabelName" ) ) {
					// this isn't the part we actually want to send back, have to find out how to deal with this well
					//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
					nextItem.TagLabelName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
				}
				else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
					//nextItem.Value = resultRowBinding[variable].Uri;
					nextItem.TagLabelUri = resultRowBinding[variable].Uri;
				}
				else if ( variable.LocalName.Equals( "taggingInstance" ) ) {
					// this isn't the part we actually want to send back, have to find out how to deal with this well
					//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
					if ( resultRowBinding[variable] != null )
						nextItem.TagInstanceUri = resultRowBinding[variable].Uri;
				}
				else if ( variable.LocalName.Equals( "foafUser" ) ) {
					//nextItem.Value = resultRowBinding[variable].Uri;
					nextItem.UserIdentifier = resultRowBinding[variable].Uri;
				}
				else if ( variable.LocalName.Equals( "taggedOn" ) ) {
					// this isn't the part we actually want to send back, have to find out how to deal with this well
					//nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
					nextItem.TagCreationTime = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
				}
				else if ( variable.LocalName.Equals( "taggedResource" ) ) {
					//nextItem.Value = resultRowBinding[variable].Uri;
					if ( resultRowBinding[variable] != null )
						nextItem.TagSubjectUri = resultRowBinding[variable].Uri;
				}
				else if ( variable.LocalName.Equals( "termPredicate" ) ) {
					//nextItem.Value = resultRowBinding[variable].Uri;
					if ( resultRowBinding[variable] != null )
						nextItem.TagPredicateUri = resultRowBinding[variable].Uri;
				}
				else if ( variable.LocalName.Equals( "termObject" ) ) {
					//nextItem.Value = resultRowBinding[variable].Uri;
					if ( resultRowBinding[variable] != null )
						nextItem.TagObjectUri = resultRowBinding[variable].Uri;
				}
			}

			nextItem.TagInstanceUri = tagInstanceUriInput;



			//items.Add(nextItem);
			//}

			return nextItem;

			//return items.ToArray();
		}
		#endregion

		#region CreateTagsOnItems
		[WebMethod( EnableSession = true )]
		public RdfTag[] CreateTagsOnItems ( RdfTag[] tagTemplates, string[] itemUrisToTag, GroupInformation[] allowedGroupUris ) {
			List<RdfTag> results = new List<RdfTag>();

			foreach ( RdfTag tagTemplate in tagTemplates ) {
				results.AddRange( CreateTagOnItems( tagTemplate, itemUrisToTag, allowedGroupUris ) );
			}

			return results.ToArray();
		}
		#endregion

		#region CreateTagOnItems
		[WebMethod( EnableSession = true )]
		public RdfTag[] CreateTagOnItems ( RdfTag tagTemplate, string[] itemUrisToTag, GroupInformation[] allowedGroupUris ) {
			List<RdfTag> results = new List<RdfTag>();

			// override what they gave us if they are not logged in
			// provides backwards compatibility with a QUTAD only call, but it is a security AND privacy risk
			if ( LoginState.AuthenticationResponse != null ) {
				tagTemplate.UserIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
			}

			foreach ( string nextItemUri in itemUrisToTag ) {
				// take all of the valid properties that were on the tag template and make up a new tag with a new timestamp
				RdfTag nextTag = tagTemplate.clone( true );

				// no excuses available for the item strings not being valid absolute URI's 
				// so we throw an exception rather than try to deal with it any other way
				if ( !Uri.IsWellFormedUriString( nextItemUri, UriKind.Absolute ) ) {
					throw new UriFormatException( nextItemUri );
				}

				// overwrite whatever one was in the template, as it is not useable for this.
				nextTag.TagSubjectUri = nextItemUri;


				if ( LoginState.AuthenticationResponse != null ) {
					nextTag.UserIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
				}

				string query = " INSERT INTO GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> \n"
								+ "     { \n"
								+ nextTag.toNTriples()
								+ "     } \n";

				SemanticResultsSink tagCreationResult;

				try {
					tagCreationResult = CommonSemanticOperations.doQuery( query );
				}
				catch ( Exception ex ) {
					throw new Exception( "Database query failed:" + query, ex );
				}

				//foreach (VariableBindings resultRowBinding in tagCreationResult.resultsList)
				//{
				//    foreach (Variable variable in resultRowBinding.Variables)
				//    {
				//        // We don't actually do anything with the result at this stage...
				//    }
				//}

				List<RdfTag> nextTagList = new List<RdfTag>( 1 );

				ChangeGroupPermissionsOnTags( nextTagList.ToArray(), allowedGroupUris, true );

				results.Add( nextTag );
			}
			return results.ToArray();
		}
		#endregion

		#region GetTagsByTemplate
		[WebMethod( EnableSession = true )]
		public RdfTag[] GetTagsByTemplate ( RdfTag tagTemplate ) {
			List<RdfTag> results = new List<RdfTag>();

			// override what they gave us if they are not logged in
			// provides backwards compatibility with a QUTAD only call, but it is a security AND privacy risk
			if ( LoginState.AuthenticationResponse != null ) {
				tagTemplate.UserIdentifier = LoginState.AuthenticationResponse.ClaimedIdentifier.ToString();
			}

			string selectedVariables = "";

			string tagInstanceUriInput = "";
			string tagLabelUriInput = "";
			string tagSubjectUriInput = "";
			string tagPredicateString = "";
			string tagObjectString = "";
			string tagFoafUserUriInput = "";


			if ( Uri.IsWellFormedUriString( tagTemplate.TagInstanceUri, UriKind.Absolute ) ) {
				tagInstanceUriInput = "<" + tagTemplate.TagInstanceUri + ">";
			}
			else {
				tagInstanceUriInput = " ?taggingInstance ";
				selectedVariables += " ?taggingInstance ";
			}

			if ( Uri.IsWellFormedUriString( tagTemplate.TagLabelUri, UriKind.Absolute ) ) {
				tagLabelUriInput = "<" + tagTemplate.TagLabelUri + ">";
			}
			else {
				tagLabelUriInput = " ?tagLabelUri ";
				selectedVariables += " ?tagLabelUri ";
			}

			if ( Uri.IsWellFormedUriString( tagTemplate.TagSubjectUri, UriKind.Absolute ) ) {
				tagSubjectUriInput = "<" + tagTemplate.TagSubjectUri + ">";
			}
			else {
				tagSubjectUriInput = " ?taggedResource ";
				selectedVariables += " ?taggedResource ";
			}


			if ( Uri.IsWellFormedUriString( tagTemplate.TagPredicateUri, UriKind.Absolute ) ) {
				tagPredicateString = tagInstanceUriInput + " terms:predicate <" + tagTemplate.TagPredicateUri + "> . ";
			}
			else {
				tagPredicateString = " OPTIONAL{ " + tagInstanceUriInput + " terms:predicate ?termPredicate . } . ";
				selectedVariables += " ?termPredicate ";
			}

			if ( Uri.IsWellFormedUriString( tagTemplate.TagObjectUri, UriKind.Absolute ) ) {
				tagObjectString = tagInstanceUriInput + " terms:object <" + tagTemplate.TagObjectUri + "> . ";
			}
			else {
				tagObjectString = " OPTIONAL{ " + tagInstanceUriInput + " terms:object ?termObject . } . ";
				selectedVariables += " ?termObject ";
			}

			if ( Uri.IsWellFormedUriString( tagTemplate.UserIdentifier, UriKind.Absolute ) ) {
				tagFoafUserUriInput = "<" + tagTemplate.UserIdentifier + ">";
			}
			else {
				tagFoafUserUriInput = " ?foafUser ";
				selectedVariables += " ?foafUser ";
			}


			if ( selectedVariables == "" ) {
				results.Add( tagTemplate );
				return results.ToArray();
			}

			string query = "    PREFIX tags: <" + NS_Constants.manager.GetNamespace( "tags" ) + "> "
							+ " PREFIX terms: <" + NS_Constants.manager.GetNamespace( "terms" ) + "> "
							+ " SELECT DISTINCT " + selectedVariables
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsSparqlGraphUri.Uri + "> "
							+ "     { "
							+ "         " + tagInstanceUriInput + " rdf:type terms:TermInstance . "
							+ "         " + tagInstanceUriInput + " terms:taggedBy " + tagFoafUserUriInput + " . "
							+ "         " + tagInstanceUriInput + " terms:taggedOn ?taggedOn . "
							+ "         " + tagInstanceUriInput + " terms:subject " + tagSubjectUriInput + " . "
							+ tagPredicateString
							+ tagObjectString
							+ "         " + tagInstanceUriInput + " terms:term " + tagLabelUriInput + " . "
							+ "         " + tagLabelUriInput + " tags:name ?tagLabelName . "
				//+ "         " + filterString
							+ "     } "
							+ " } ";
			//+ " LIMIT " + maxNumberOfResults + " ";
			// Peter_to_do: listen to the paging value
			//+ " ORDER BY ?tagLabelName ";

			SemanticResultsSink queryResults;

			try {
				queryResults = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed:" + query, ex );
			}

			foreach ( VariableBindings resultRowBinding in queryResults.resultsList ) {
				RdfTag nextItem = tagTemplate.clone( false );

				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "tagLabelName" ) ) {
						nextItem.TagLabelName = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "tagLabelUri" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.TagLabelUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggingInstance" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.TagInstanceUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "foafUser" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.UserIdentifier = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "taggedOn" ) ) {
						nextItem.TagCreationTime = SemanticUtility.resolveResourceReferenceString( resultRowBinding[variable] );
					}
					else if ( variable.LocalName.Equals( "taggedResource" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.TagSubjectUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termPredicate" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.TagPredicateUri = resultRowBinding[variable].Uri;
					}
					else if ( variable.LocalName.Equals( "termObject" ) ) {
						if ( resultRowBinding[variable] != null )
							nextItem.TagObjectUri = resultRowBinding[variable].Uri;
					}
				}


				results.Add( nextItem );
			}

			return results.ToArray();
		}
		#endregion

		#region DeleteTags
		[WebMethod( EnableSession = true )]
		public RdfTag[] DeleteTags ( RdfTag[] tags ) {
			List<RdfTag> results = new List<RdfTag>();

			foreach ( RdfTag nextTag in tags ) {
				results.AddRange( DeleteTag( nextTag ) );
			}

			return results.ToArray();
		}
		#endregion

		#region DeleteTag
		// returns a list of successfully deleted tags that the user agent can then remove from the view
		// Peter_to_do: Implement me
		[WebMethod( EnableSession = true )]
		public RdfTag[] DeleteTag ( RdfTag tag ) {
			// the tag object should have at least a tagInstanceUri, if possible
			// alternatively, the tag could contain a label, user, and subject minimum
			// alternatively, the tag could contain a labelUri, user, and subject

			// note, users are the only ones who can delete their own tags

			// Peter_to_do: how are anonymous tags deleted?


			// by default we indicate that we don't delete tags until we decide whether we want to or not... we don't even hide them yet
			return new List<RdfTag>().ToArray();
		}
		#endregion

		#region ChangeGroupPermissionsOnTags
		/// <summary>
		/// Provides the ability to update the group level permissions that are available on one or more tags.
		/// Note that to make a tag public the group informations here here should include the URI matching that in ENTITY_CONSTANTS.termsAnonymousGroup
		/// </summary>
		/// <param name="tagToChangePermissionFor">tagToGivePermissionFor only needs to have a valid URI for TagInstanceUri</param>
		/// <param name="groupInformation">groupInformation only needs to have a valid URI for GroupUri</param>
		/// <param name="givePermission">givePermission is true to enable permission or false to disable permission for a given groups on each of the given tags</param>
		/// <returns>boolean value indicating whether the change was successful for all of the groups on all of the tags.</returns>
		[WebMethod( EnableSession = true )]
		public bool ChangeGroupPermissionsOnTags ( RdfTag[] tagsToChangePermissionFor, GroupInformation[] groupInformationList, bool givePermission ) {
			foreach ( GroupInformation nextGroupInformation in groupInformationList ) {
				if ( !Uri.IsWellFormedUriString( nextGroupInformation.GroupUri, UriKind.Absolute ) ) {
					throw new UriFormatException( "Group URI was invalid" + nextGroupInformation.GroupUri );
				}
			}


			foreach ( RdfTag nextTagToCheck in tagsToChangePermissionFor ) {
				if ( !Uri.IsWellFormedUriString( nextTagToCheck.TagInstanceUri, UriKind.Absolute ) ) {
					throw new UriFormatException( "Tag Instance URI was invalid" + nextTagToCheck.TagInstanceUri );
				}
			}

			// http://docs.openlinksw.com/virtuoso/rdfsparul.html

			// 0. Check the logged in user and make sure it matches the user who created the tag instance (SPARQL ASK or SELECT)
			// Alternatively, use a specific graph name to determine administrator logins who can make these changes for particular groups

			// 1. Ask whether there is a permission matching this tag and group (SPARQL ASK or SELECT)

			// 2. If there was a permission and givePermission is false, delete the permission (SPARQL/Update DELETE)

			// 3. If there wasn't a permission and givePermission is true, insert a new permission (SPARQL/Update INSERT)

			// 4. if an error occured do we need a rollback mechanism, and still return false?

			// Peter_to_do: Implement me!!!
			return true;
		}
		#endregion

		#region GetLabelsForUris
		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetLabelsForUri ( string uriToGetLabelsFor ) {
			List<string> uriList = new List<string>( 1 );

			if ( Uri.IsWellFormedUriString( uriToGetLabelsFor, UriKind.Absolute ) ) {
				uriList.Add( uriToGetLabelsFor );
				return GetLabelsForUris( uriList.ToArray() );
			}
			else {
				throw new UriFormatException( "URI was not valid" );
			}
		}
		#endregion

		#region GetGraphsUsefulForPredicate
		[WebMethod( EnableSession = true )]
		public string[] GetGraphsUsefulForPredicate ( string givenPredicate ) {
			return GetGraphsUsefulFor( givenPredicate, Entity_Constants.termsUsefulForPredicate.Uri );
		}
		#endregion

		#region GetGraphsUsefulFor
		[WebMethod( EnableSession = true )]
		public string[] GetGraphsUsefulFor ( string givenResource, string predicateToCheck ) {
			if ( !Uri.IsWellFormedUriString( givenResource, UriKind.Absolute ) ) {
				throw new UriFormatException( "Given resource was not a valid URI" );
			}
			else if ( !Uri.IsWellFormedUriString( predicateToCheck, UriKind.Absolute ) ) {
				throw new UriFormatException( "Predicate to check was not a valid URI" );
			}

			// Peter_to_do: optimise this process by statically caching this information, as it won't change very often at all
			//if(usefulGraphsForUriCache.ContainsKey(predicateToCheck)

			string query = "    SELECT DISTINCT ?graph "
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsGraphIndexUri.Uri + "> "
							+ "     { "
							+ "       ?graph  <" + predicateToCheck + "> <" + givenResource + "> . "
							+ "     } "
							+ " } ";

			SemanticResultsSink results;

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed", ex );
			}

			List<string> resultsList = new List<string>( results.resultsList.Count );

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "graph" ) ) {
						if ( resultRowBinding[variable] != null )
							resultsList.Add( resultRowBinding[variable].Uri );
					}
				}
			}

			return resultsList.ToArray();
		}
		#endregion

		#region GetAllKnownGraphs
		[WebMethod( EnableSession = true )]
		private string[] GetAllKnownGraphs () {
			string query = "    SELECT DISTINCT ?graph "
							+ " WHERE "
							+ " { "
							+ "     GRAPH <" + Entity_Constants.termsGraphIndexUri.Uri + "> "
							+ "     { "
							+ "       ?graph ?p ?o . "
							+ "     } "
							+ " } ";

			SemanticResultsSink results;

			try {
				results = CommonSemanticOperations.doQuery( query );
			}
			catch ( Exception ex ) {
				throw new Exception( "Database query failed", ex );
			}

			List<string> resultsList = new List<string>( results.resultsList.Count );

			foreach ( VariableBindings resultRowBinding in results.resultsList ) {
				foreach ( Variable variable in resultRowBinding.Variables ) {
					if ( variable.LocalName.Equals( "graph" ) ) {
						if ( resultRowBinding[variable] != null )
							resultsList.Add( resultRowBinding[variable].Uri );
					}
				}
			}

			return resultsList.ToArray();
		}
		#endregion

		#region GetLabelsForUris
		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetLabelsForUris ( string[] urisToGetLabelsFor ) {
			List<SubjectPredicateObject> resultsList = new List<SubjectPredicateObject>( urisToGetLabelsFor.Length );

			List<string> queryStrings = new List<string>();

			foreach ( string nextUriToGetLabelFor in urisToGetLabelsFor ) {
				if ( Uri.IsWellFormedUriString( nextUriToGetLabelFor, UriKind.Absolute ) ) {
					queryStrings.Add( CommonSemanticOperations.transformBio2RDFUriForQuery( nextUriToGetLabelFor, "label" ) );
				}
			}

			resultsList.AddRange( GetRdfInformationForUris( queryStrings.ToArray() ) );

			//string[] labelSearchGraphs = GetGraphsUsefulForPredicate(Entity_Constants.rdfsLabel.Uri);

			//foreach (string nextUri in urisToGetLabelsFor)
			//{
			//    string filterString;

			//    if (Uri.IsWellFormedUriString(nextUri, UriKind.Absolute))
			//    {
			//        validUriList.Add(nextUri);
			//        filterString = " <" + nextUri + "> rdfs:label ?label . ";
			//    }
			//    else
			//    {
			//        throw new UriFormatException("One of the given uri's was not valid:" + nextUri);
			//    }

			//    foreach(string nextGraph in labelSearchGraphs)
			//    {

			//        string query = "    PREFIX rdfs: <"+NS_Constants.manager.GetNamespace("rdfs")+"> "
			//                        + " SELECT DISTINCT ?label "
			//                        + " WHERE "
			//                        + " { "
			//                        + "     GRAPH <"+nextGraph+"> "
			//                        + "     { "
			//                        + filterString
			//                        + "     } "
			//                        + " } ";
			//                        //+ " LIMIT " + maxNumberOfResults + " ";
			//        // Peter_to_do: listen to the paging value
			//        //+ " ORDER BY ?tagLabelName ";

			//        SemanticResultsSink results;

			//        try
			//        {
			//            results = CommonSemanticOperations.doQuery(query);
			//        }
			//        catch (Exception ex)
			//        {
			//            throw new Exception("Database query failed", ex);
			//            //RdfTag resultTag = new RdfTag();
			//            //resultTag.TagLabelName = query;
			//            //UriAndLabel result = new UriAndLabel();
			//            //result.ItemLabel = query;

			//            //items.Add(resultTag);
			//            //return items.ToArray();
			//        }

			//        foreach (VariableBindings resultRowBinding in results.resultsList)
			//        {
			//            //ListItem nextItem = new ListItem();
			//            UriAndLabel nextItem = new UriAndLabel();
			//            //RdfTag nextItem = new RdfTag();
			//            foreach (Variable variable in resultRowBinding.Variables)
			//            {
			//                if (variable.LocalName.Equals("label"))
			//                {
			//                    // this isn't the part we actually want to send back, have to find out how to deal with this well
			//                    //nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
			//                    if(resultRowBinding[variable] != null)
			//                        nextItem.ItemLabel = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
			//                }
			//                else if (variable.LocalName.Equals("givenUri"))
			//                {
			//                    //nextItem.Value = resultRowBinding[variable].Uri;
			//                    if (resultRowBinding[variable] != null)
			//                        nextItem.ItemUri = resultRowBinding[variable].Uri;
			//                }
			//            }

			//            nextItem.ItemUri = nextUri;

			//            resultsList.Add(nextItem);
			//        }
			//    }

			//}

			return resultsList.ToArray();

		}
		#endregion

		#region GetRdfInformationForUris
		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetRdfInformationForUris ( string[] resourceUris ) {
			List<SubjectPredicateObject> results = new List<SubjectPredicateObject>();

			foreach ( string nextIndividualUri in resourceUris ) {
				results.AddRange( GetRdfInformationForUri( nextIndividualUri ) );
			}

			return results.ToArray();
		}
		#endregion

		#region GetRdfInformationForUri
		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetRdfInformationForUri ( string resourceUri )//, string optionalTypeUri, int maximumNumberOfResults, int offset, bool getLabelsForEachPredicateAndObject)
		{
			// Peter_to_do: maybe only allow access to this method if logged in to prevent abuse where people should be using http://bio2rdf.org/db:id instead
			List<SubjectPredicateObject> resultsList = new List<SubjectPredicateObject>();

			//string[] semanticSearchGraphs;

			//if (optionalTypeUri == null || optionalTypeUri.Trim().Equals(""))
			//{
			//    semanticSearchGraphs = GetAllKnownGraphs();
			//}
			//else
			//{
			//    if (Uri.IsWellFormedUriString(optionalTypeUri,UriKind.Absolute))
			//    {
			//        semanticSearchGraphs = GetGraphsUsefulFor(optionalTypeUri, Entity_Constants.termsUsefulForType.Uri);
			//    }
			//    else
			//    {
			//        throw new UriFormatException("If the optional RDF type URI is present it must be a valid URI!");
			//    }
			//}

			//string filterString = "";

			//if (!Uri.IsWellFormedUriString(resourceUri, UriKind.Absolute))
			//{
			//    throw new UriFormatException("The given URI was not valid:" + resourceUri);
			//}

			//foreach (string nextGraph in semanticSearchGraphs)
			//{
			//    string query = "    PREFIX rdfs: <" + NS_Constants.manager.GetNamespace("rdfs") + "> "
			//                    + " SELECT DISTINCT ?predicate ?object "
			//                    + " WHERE "
			//                    + " { "
			//                    + "     GRAPH <" + nextGraph + "> "
			//                    + "     { "
			//                    + "        <" + resourceUri + "> ?predicate ?object . "
			//                    + filterString
			//                    + "     } "
			//                    + " } "
			//                    +" LIMIT " + maximumNumberOfResults + " ";
			//    // Peter_to_do: listen to the paging value
			//    //+ " ORDER BY ?subject ?predicate ?object ";

			//    SemanticResultsSink results;

			//    try
			//    {
			//        results = CommonSemanticOperations.doQuery(query,"http://bio2rdf.mquter.qut.edu.au:8890/sparql");

			//    }
			//    catch (Exception ex)
			//    {
			//        throw new Exception("Database query failed: " + query, ex);
			//        //RdfTag resultTag = new RdfTag();
			//        //resultTag.TagLabelName = query;
			//        //UriAndLabel result = new UriAndLabel();
			//        //result.ItemLabel = query;

			//        //items.Add(resultTag);
			//        //return items.ToArray();
			//    }

			//    foreach (VariableBindings resultRowBinding in results.resultsList)
			//    {
			//        SubjectPredicateObject nextItem = new SubjectPredicateObject();
			//        //ListItem nextItem = new ListItem();
			//        //UriAndLabel nextItem = new UriAndLabel();
			//        //RdfTag nextItem = new RdfTag();
			//        foreach (Variable variable in resultRowBinding.Variables)
			//        {
			//            if (variable.LocalName.Equals("subject"))
			//            {
			//                // this isn't the part we actually want to send back, have to find out how to deal with this well
			//                //nextItem.Text = SemanticUtility.resolveResourceReferenceString(resultRowBinding[variable]);
			//                nextItem.SubjectUri = resultRowBinding[variable].Uri;
			//            }
			//            else if (variable.LocalName.Equals("predicate"))
			//            {
			//                //nextItem.Value = resultRowBinding[variable].Uri;
			//                nextItem.PredicateUri = resultRowBinding[variable].Uri;
			//                if (getLabelsForEachPredicateAndObject)
			//                {
			//                    nextItem.PredicateLabels = GetLabelsForUri(nextItem.PredicateUri);
			//                }
			//            }
			//            else if (variable.LocalName.Equals("object"))
			//            {
			//                if (resultRowBinding[variable] is Literal)
			//                {
			//                    nextItem.ObjectType = "Literal";
			//                    nextItem.ObjectResource = (resultRowBinding[variable] as Literal).Value;
			//                }
			//                else
			//                {
			//                    nextItem.ObjectType = "URI";
			//                    nextItem.ObjectResource = (resultRowBinding[variable] as Entity).Uri;

			//                    if (getLabelsForEachPredicateAndObject)
			//                    {
			//                        nextItem.ObjectLabels = GetLabelsForUri(nextItem.ObjectResource);
			//                    }
			//                }
			//            }
			//        }

			//        //nextItem.ItemUri = nextUri;
			//        nextItem.SubjectUri = resourceUri;
			//        resultsList.Add(nextItem);
			//    }
			//}

			//return resultsList.ToArray();
			Statement[] resultStatements = CommonSemanticOperations.getRdfFromUrl( resourceUri );

			foreach ( Statement nextResultStatement in resultStatements ) {
				resultsList.Add( new SubjectPredicateObject( nextResultStatement ) );
			}

			return resultsList.ToArray();
		}
		#endregion

		#region GetRdfInformationForUriExcludingBio2RDFDefaults
		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetRdfInformationForUriExcludingBio2RDFDefaults ( string nextIdentifierUri, bool removeBio2RDFDefaultStatements ) {
			if ( !removeBio2RDFDefaultStatements )
				return GetRdfInformationForUri( nextIdentifierUri );

			// Peter_to_do: in future convert the URL to one like http://bio2rdf.org/constructnoextras/ns:id so that it is known not to have the extra statements inserted

			List<SubjectPredicateObject> resultList = new List<SubjectPredicateObject>( GetRdfInformationForUri( nextIdentifierUri ) );
			foreach ( SubjectPredicateObject nextStatement in resultList ) {
				if ( !CommonSemanticOperations.predicateIsBio2RDFDefault( nextStatement.PredicateUri ) )
					resultList.Add( nextStatement );
			}

			return resultList.ToArray();
		}
		#endregion

		public class SubjectPredicate {
			public string Subject {
				get;
				set;
			}
			public string Predicate {
				get;
				set;
			}
		}

		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetObject ( string subjectUri, string predicateUri ) {
			List<SubjectPredicateObject> results = new List<SubjectPredicateObject>();

			GetObject( subjectUri, predicateUri, results );

			return results.ToArray();
		}

		[WebMethod( EnableSession = true )]
		public SubjectPredicateObject[] GetObjects ( SubjectPredicate[] subjectPredicates ) {
			List<SubjectPredicateObject> results = new List<SubjectPredicateObject>();

			foreach ( SubjectPredicate queryPair in subjectPredicates ) {
				GetObject( queryPair.Subject, queryPair.Predicate, results );
			}

			return results.ToArray();
		}

		private static void GetObject ( string subjectUri, string predicateUri, List<SubjectPredicateObject> resultsList ) {
			if ( !Uri.IsWellFormedUriString( subjectUri, UriKind.Absolute ) ) {
				throw new UriFormatException( "The subject URI was not valid:" + subjectUri );
			}

			if ( !Uri.IsWellFormedUriString( predicateUri, UriKind.Absolute ) ) {
				throw new UriFormatException( "The subject URI was not valid:" + predicateUri );
			}

			resultsList.AddRange( from triple in CommonSemanticOperations.getRdfFromUrl( subjectUri )
								  where triple.Predicate == predicateUri
								  select new SubjectPredicateObject( triple ) );
		}

	}
}